package com.yyw.foodie.controller.user;

import com.yyw.foodie.bo.cart.ShopCartBo;
import com.yyw.foodie.bo.users.UserLoginBo;
import com.yyw.foodie.bo.users.UserRegisterBo;
import com.yyw.foodie.common.Result;
import com.yyw.foodie.constant.BaseConstant;
import com.yyw.foodie.pojo.UsersEntity;
import com.yyw.foodie.service.UsersService;
import com.yyw.foodie.utils.BindingResultJoinError;
import com.yyw.foodie.utils.CookieUtils;
import com.yyw.foodie.utils.JsonUtils;
import com.yyw.foodie.utils.RedisOperator;
import com.yyw.foodie.vo.UsersVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.*;

/**
 * @author 易煜玮
 * @ClassName PassPortController.java
 * @Description 用户登录注册相关控制器
 * @createTime 2021年05月22日 19:36:00
 */
@Api(value = "用户登录注册相关", tags = "用户登录注册相关")
@RestController
@RequestMapping("/passport")
public class PassPortController {

    @Autowired
    private UsersService usersService;

    @Autowired
    private RedisOperator redisOperator;

    /**
     * 注册时判断用户名是否已存在
     *
     * @param userName 用户名
     * @return true = 不存在  false = 存在
     */
    @ApiOperation(value = "判断用户名是否存在", notes = "用户注册时判断用户名是否存在", httpMethod = "GET")
    @GetMapping("usernameIsExist/{userName}")
    public Result getUserIsExists(@PathVariable String userName) {
        if (StringUtils.isBlank(userName)) {
            return Result.errorException("用户名不能为空");
        }
        Boolean result = usersService.userNameIsExists(userName);
        return result ? Result.ok() : Result.errorMsg("该用户名已存在!");
    }

    /**
     * 用户注册
     *
     * @param userRegisterBo 封装用户数据
     * @param bindingResult  校验数据
     * @return 注册后的用户数据
     */
    @ApiOperation(value = "用户注册", notes = "用户注册操作", httpMethod = "POST")
    @PostMapping("/register")
    public Result register(@RequestBody @Valid UserRegisterBo userRegisterBo, BindingResult bindingResult,
                           HttpServletRequest request, HttpServletResponse response) {
        // 0. 校验数据完整性
        Map<String, String> map = BindingResultJoinError.joinErrorMsg(bindingResult);
        if (null != map) {
            return Result.errorMap(map);
        }
        // 1. 校验两次密码是否正确
        String passWord = userRegisterBo.getPassWord();
        String configPassWord = userRegisterBo.getConfigPassWord();
        if (!Objects.equals(passWord, configPassWord)) {
            return Result.errorMsg("两次输入的密码不一致");
        }

        // 2. 校验用户名是否存在
        String userName = userRegisterBo.getUserName();
        Boolean userNameResult = usersService.userNameIsExists(userName);
        if (!userNameResult) {
            return Result.errorMsg("用户名已存在");
        }

        // 3. 保存操作
        UsersEntity entity = usersService.register(userRegisterBo);
        UsersVo vo = new UsersVo();
        BeanUtils.copyProperties(entity, vo);
        vo.setUsername(entity.getUserName());
        vo.setNickname(entity.getNickName());

        // 5. 保存会话信息至redis
        vo = generateUserToken2Redis(vo);

        // 4. 存储cookie用户信息
        CookieUtils.setCookie(request, response, "user", JsonUtils.objectToJson(vo), true);

        // 6. 同步购物车数据
        syncUserShopCat(entity.getId(), request, response);

        return Result.ok(entity);
    }


    /**
     * 用户登录
     *
     * @param userLogin     用户登录账户密码
     * @param bindingResult 校验数据
     * @return 登录成功/失败
     */
    @ApiOperation(value = "用户登录", notes = "用户登录操作", httpMethod = "POST")
    @PostMapping("/login")
    public Result login(@RequestBody @Valid UserLoginBo userLogin, BindingResult bindingResult,
                        HttpServletRequest request, HttpServletResponse response) {
        Map<String, String> map = BindingResultJoinError.joinErrorMsg(bindingResult);
        if (null != map) {
            return Result.errorMap(map);
        }
        try {
            UsersEntity entity = usersService.login(userLogin);
            if (null == entity) {
                return Result.errorMsg("用户名或密码输入错误");
            }
            UsersVo vo = new UsersVo();
            BeanUtils.copyProperties(entity, vo);
            vo.setUsername(entity.getUserName());
            vo.setNickname(entity.getNickName());

            // 保存会话信息至redis
            vo = generateUserToken2Redis(vo);

            // 存储cookie用户信息
            CookieUtils.setCookie(request, response, "user", JsonUtils.objectToJson(vo), true);

            // 同步购物车数据
            syncUserShopCat(entity.getId(), request, response);

            return Result.ok(vo);
        } catch (Exception e) {
            e.printStackTrace();
            return Result.errorException(e.getMessage());
        }
    }


    @ApiOperation(value = "用户注销", notes = "退出登录操作")
    @PostMapping("/logout")
    public Result logout(@RequestParam String userId, HttpServletRequest request, HttpServletResponse response) {
        // 删除cookie信息
        CookieUtils.deleteCookie(request, response, "user");
        // 删除用户本地的cookie购物车数据
        CookieUtils.deleteCookie(request, response, BaseConstant.SHOPCART);
        // 删除redis的token信息
        redisOperator.del(BaseConstant.USER_TOKEN_REDIS_KEY + userId);
        return Result.ok();
    }


    /**
     * 用户登录、注册后同步购物车数据
     * 1. redis存在数据，cookie没有数据，同步cookie
     * 2. redis没有数据，cookie有数据，同步到redis
     * 3. redis有数据，cookie有数据，如果都存在同一个商品，就以cookie商品的数据为准，最后合并商品
     * @param userId
     * @param request
     * @param response
     */
    private void syncUserShopCat(String userId, HttpServletRequest request, HttpServletResponse response) {
        String shopCatRedis = redisOperator.get(BaseConstant.CACHE_CAT + userId);
        String shopCatCookie = CookieUtils.getCookieValue(request, BaseConstant.SHOPCART, true);

        if (StringUtils.isNotBlank(shopCatRedis)) {
            if (StringUtils.isBlank(shopCatCookie)) {
                // 1. redis存在数据，cookie没有数据，同步给cookie
                CookieUtils.setCookie(request, response, BaseConstant.SHOPCART, shopCatRedis ,true);
            } else {
                // 3. redis有数据，cookie有数据，如果都存在同一个商品，就以cookie商品的数据为准，最后合并商品
                List<ShopCartBo> shopCartBoCookie = JsonUtils.jsonToList(shopCatCookie, ShopCartBo.class);
                List<ShopCartBo> shopCartBoRedis = JsonUtils.jsonToList(shopCatRedis, ShopCartBo.class);

                List<ShopCartBo> removeEl = new ArrayList<>();

                // 循环列表，如果两个集合存在相同的sku，则以cookie的数量为准
                for (ShopCartBo redisBo : shopCartBoRedis) {
                    for (ShopCartBo cookieBo : shopCartBoCookie) {
                        if (Objects.equals(cookieBo.getSpecId(), redisBo.getSpecId())) {
                            redisBo.setBuyCounts(cookieBo.getBuyCounts());
                            removeEl.add(cookieBo);
                        }
                    }
                }

                // 删除相同sku的数据，再合并购物车
                shopCartBoCookie.removeAll(removeEl);
                shopCartBoRedis.addAll(shopCartBoCookie);

                // 同步Redis和Cookie
                String val = JsonUtils.objectToJson(shopCartBoRedis);
                redisOperator.set(BaseConstant.CACHE_CAT + userId, val);
                CookieUtils.setCookie(request, response, BaseConstant.SHOPCART, val ,true);
            }
        }else {
            if (StringUtils.isNotBlank(shopCatCookie)) {
                // 2. redis没有数据，cookie有数据，同步到redis
                redisOperator.set(BaseConstant.CACHE_CAT + userId, shopCatCookie);
            }
        }
    }



    /**
     * 实现Redis分布式会话
     * @param vo
     * @return
     */
    private UsersVo generateUserToken2Redis(UsersVo vo) {
        // 生成token
        String uuid = UUID.randomUUID().toString().trim();
        vo.setUserUniqueToken(uuid);
        // 保存至redis
        redisOperator.set(BaseConstant.USER_TOKEN_REDIS_KEY + vo.getId(), uuid);
        return vo;
    }
}
